package org.weasel.controllers;

import java.util.ArrayList;

import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TreeItem;
import org.weasel.business.Company;
import org.weasel.business.IDFactory;
import org.weasel.business.Iteration;
import org.weasel.business.Project;
import org.weasel.business.Release;
import org.weasel.business.Unscheduled;
import org.weasel.business.UserStory;
import org.weasel.models.ChangeListener;
import org.weasel.models.Model;
import org.weasel.models.TreeModel;
import org.weasel.tree.TreeObject;
import org.weasel.tree.TreeObjectCompany;
import org.weasel.tree.TreeObjectIteration;
import org.weasel.tree.TreeObjectProject;
import org.weasel.tree.TreeObjectRelease;
import org.weasel.tree.TreeObjectUnscheduled;
import org.weasel.tree.TreeObjectUserStory;
import org.weasel.tree.TreeParent;
import org.weasel.views.Navigator;
import org.weasel.wizards.CreateDeveloperWizard;
import org.weasel.wizards.ProjectWizard;
import org.weasel.wizards.UserStoryWizard;

public class TreeController implements ChangeListener{
	private TreeModel treemodel;
	private ApplicationController appController;
	private Model model;
	private Navigator navigator;
	private static TreeController instance = null;
	private TreeViewer viewer;
	
	private static TreeParent invisibleRoot;

	private TreeController() {}
	
	public static TreeController instance() {
		if(instance == null)
			instance = new TreeController();
		return instance;
	}
	
	public Navigator getNavigator() {
		return this.navigator;
	}

	
	public void setApplicationController(ApplicationController appController) {
		this.appController = appController;
		this.model = appController.getModel();
	}
	
	public TreeParent displayTree(){
		try {
			treemodel = new TreeModel(model);
			invisibleRoot = treemodel.loadTree();
			viewer.setInput(invisibleRoot);
			
			// sets the company as expanded on startup
			TreeObject to_company = invisibleRoot.getChildren()[0];
			if (to_company != null) {
				viewer.setExpandedState(to_company, true);
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}
	
	public static TreeParent getTree() {
		return invisibleRoot;
	}
	
	public void setNavigator(Navigator navigator){
		this.navigator = navigator;
	}
	
	public boolean existUnsavedChanges() {
		if(navigator.getSaveAction().isEnabled())
			return true;
		else 
			return false;
	}
	
	public void saveChanges() {
		appController.saveModel();
	}
	
	/**
	 * This method enables the save button of the tree.
	 * It should be used after every modification operation to show the user
	 * that something has changed that needes to be stored.
	 *
	 */
	public void enableSaveButton() {
		this.navigator.getSaveAction().setEnabled(true);
	}
	
	/**
	 * This method disables the save button.
	 * It is called after a click on the save button indicating that all changes
	 * are stored permanently on disk.
	 *
	 */
	public void disableSaveButton() {
		this.navigator.getSaveAction().setEnabled(false);
	}
	
	//FIXME Should be moved to the application controller, since this method
	//exists also in the DeveloperViewController (where it should also be). So an alternative
	//is to either define an action for it, or to put it in the ApplicationController, where it is
	//then accessed from both, the TreeController and DeveloperViewController
	public void showDeveloperWizard() {
		CreateDeveloperWizard wizard = new CreateDeveloperWizard(model);
		if (wizard != null) {
			WizardDialog dialog = new WizardDialog(viewer.getTree().getShell(), wizard);
			dialog.setPageSize(new Point(450, 250));
	        dialog.open();
	        appController.changesOccured();
		}
	}
	
	public void showProjectWizard() {
		if(model.getCompany().getDevelopers().size()==0){
			MessageDialog.openWarning(viewer.getTree().getShell(), "Warning", "There are no company developers registered!\nPlease add some before creating a new project");
			showDeveloperWizard();
		}
			
		ProjectWizard wizard = new ProjectWizard(model);
		if (wizard != null) {
			WizardDialog dialog = new WizardDialog(viewer.getTree().getShell(), wizard);
			dialog.setPageSize(new Point(450, 250));
	        dialog.open();
	        appController.changesOccured();
		}
	
	}
	
	public void showUserStoryWizard(TreeParent selectedTreeObject) {
		TreeObjectProject to_project = getProjectOfItem(selectedTreeObject);
		Shell shell = viewer.getTree().getShell();
		UserStoryWizard wizard = new UserStoryWizard(to_project, selectedTreeObject);
		if(wizard != null) {
			WizardDialog dialog = new WizardDialog(shell, wizard);
	        dialog.setPageSize(new Point(450, 250));
	        dialog.open();
	        appController.changesOccured();
		}
	}
	
	/**
	 * Processed, if a double-click event is catched in the Navigator-view
	 *
	 */
	public void doDoubleClickAction(){
		TreeObject treeObject = getSelectedTreeObject();
		if(treeObject instanceof TreeObjectUserStory){
			//show the view
			appController.showUserStoryView((TreeObjectUserStory)treeObject, getProjectOfItem(treeObject).getProject());
		} else {
			//expand/collapse treeparent
			viewer.setExpandedState(treeObject, true);
			
			if(treeObject instanceof TreeObjectProject) {
				appController.showProjectView((TreeObjectProject) treeObject);
			} else if(treeObject instanceof TreeObjectRelease) {
				appController.showReleaseView((TreeObjectRelease) treeObject);
			} else if(treeObject instanceof TreeObjectIteration) {
				appController.showIterationView((TreeObjectIteration) treeObject);
			}
		}
	}
	
	/**
	 * This method goes recursively to the treeobject which represents the project
	 * and returns the project object of that treeobject.
	 * @param object
	 * @return
	 */
	private TreeObjectProject getProjectOfItem(TreeObject object) {
		while (!(object instanceof TreeObjectProject)) {
			object = object.getParent();
		}
		return (TreeObjectProject)object;
	}
	
	public void setTreeViewer(TreeViewer viewer) {
		this.viewer = viewer;
	}
	
	/**
	 * Does both, adding the given Story to the model and to the TreeModel for displaying
	 * it in the navigator
	 * @param userStory
	 * @param treeParent
	 */
	public void addUserStory(UserStory userStory, TreeParent treeParent) {
		TreeObjectUserStory to_userStory = new TreeObjectUserStory(userStory.getName(), userStory);
		if(treeParent instanceof TreeObjectIteration) {
			TreeObjectIteration to_iteration = (TreeObjectIteration) treeParent;
			Iteration iteration = to_iteration.getIteration();
			iteration.addUserStory(userStory);
		} else if(treeParent instanceof TreeObjectRelease) {
			TreeObjectRelease to_release = (TreeObjectRelease) treeParent;
			Release release = to_release.getRelease();
			release.getUnscheduled().addUserStory(userStory);
		} else if(treeParent instanceof TreeObjectProject) {
			TreeObjectProject to_project = (TreeObjectProject) treeParent;
			Project project = to_project.getProject();
			project.getUnscheduled().addUserStory(userStory);
		} else if(treeParent instanceof TreeObjectUnscheduled) {
			TreeObjectUnscheduled to_unscheduled = (TreeObjectUnscheduled) treeParent;
			Unscheduled unscheduled = to_unscheduled.getUnscheduled();
			unscheduled.addUserStory(userStory);
		}
		treeParent.addChild(to_userStory);
		enableSaveButton();
		refreshViewer();
	}
	
	/**
	 * used for Drag&Drop
	 * @param to_userStory
	 * @param tp_destination
	 * @param tp_source
	 */
	public void moveUserStory(TreeObject to_userStory, TreeParent tp_destination, TreeParent tp_source) {
		UserStory userStory = ((TreeObjectUserStory) to_userStory).getUserStory();
		if(tp_destination instanceof TreeObjectIteration) {
			TreeObjectIteration to_iteration = (TreeObjectIteration) tp_destination;
			to_iteration.getIteration().addUserStory(userStory);
		} else if(tp_destination instanceof TreeObjectUnscheduled) {
			TreeObjectUnscheduled to_unscheduled = (TreeObjectUnscheduled) tp_destination;
			to_unscheduled.getUnscheduled().addUserStory(userStory);
		}
		if(tp_source instanceof TreeObjectIteration) {
			TreeObjectIteration to_iteration = (TreeObjectIteration) tp_source;
			to_iteration.getIteration().removeUserStory(userStory.getId());
			
		} else if(tp_source instanceof TreeObjectUnscheduled) {
			TreeObjectUnscheduled to_unscheduled = (TreeObjectUnscheduled) tp_source;
			to_unscheduled.getUnscheduled().removeUserStory(userStory.getId());
		}
		tp_source.removeChild(to_userStory);
		tp_destination.addChild(to_userStory);
		appController.changesOccured();
		refreshViewer();
	}
	

	
	/**
	 * This method shows a confirm dialog to the user and asks if the project, release, iteration or userStory should be deleted.
	 * It returns a boolean which is true if the user has clicked yes and false otherwise.
	 * The paramaters are the following:
	 * type... release, project, iteration or user story
	 * name... releasename, projectname
	 * @param text
	 * @return
	 */
	private boolean isDeletionConfirmed(String type, String name) {
		return MessageDialog.openConfirm(viewer.getTree().getShell(), "Confirm", "Do You want to delete the " + type + " \"" + name + "\"?");
	}
	
	/**
	 * This method expands all the childs of a treeParent passed as parameter
	 * 
	 * @param parent
	 */
	public void expandChilds(TreeObject node) {
		if (node instanceof TreeParent) {
			TreeParent parent = (TreeParent) node;
			viewer.setExpandedState(parent, true);
			if (parent.hasChildren()) {
				for (TreeObject child : parent.getChildren()) {
					expandChilds(child);
				}
			}
		}
	}
	
	/**
	 * This method expands all the childs of a treeParent passed as parameter
	 * 
	 * @param parent
	 */
	public void collapseChilds(TreeObject node) {
		if (node instanceof TreeParent) {
			TreeParent parent = (TreeParent) node;
			viewer.setExpandedState(parent, false);
			if (parent.hasChildren()) {
				for (TreeObject child : parent.getChildren()) {
					collapseChilds(child);
				}
			}
		}
	}
	
	public void addProject(Project project, TreeObjectProject to_project) {
		TreeItem item = viewer.getTree().getItem(0);
		TreeParent object = (TreeParent)item.getData();
		TreeObjectCompany to_company = (TreeObjectCompany)object;
		
		//TreeObjectProject to_project = new TreeObjectProject(project.getName(), project);
		to_company.addChild(to_project);
		
		Company company = to_company.getCompany();
		company.addProject(project);
		appController.changesOccured();
		
		refreshViewer();
	}


	
	private TreeObject getSelectedTreeObject() {
		try{
			ISelection selection = viewer.getSelection();
			TreeObject selectedTreeObject = (TreeObject) ((IStructuredSelection) selection).getFirstElement();
			return selectedTreeObject;	
		}catch (Exception e){
			System.out.println("Exception caught: Navigator.getSelectedTreeObject()");
			return null;
		}
	}

	public void refreshViewer() {
		viewer.refresh();
	}

	public void removeSelectedObject() {
		TreeObject to_selected = getSelectedTreeObject();
		if (to_selected instanceof TreeObjectProject) {
			removeProject();
		} else if (to_selected instanceof TreeObjectUserStory) {
			removeUserStory();	
		} else if(to_selected instanceof TreeObjectRelease) {
			removeRelease();
		} else if(to_selected instanceof TreeObjectIteration) {
			removeIteration();
		}
	}
	
	private void removeProject() {
		TreeObjectProject to_project = (TreeObjectProject) getSelectedTreeObject();
		if(isDeletionConfirmed("project", to_project.getName())) {
			model.getCompany().removeProject(to_project.getProject().getId());
			treemodel.removeTreeObject(to_project);
			refreshViewer();
			enableSaveButton(); //can be left at it is
		}
	}
	
	private void removeRelease() {
		TreeObjectRelease to_release = (TreeObjectRelease) getSelectedTreeObject();
		if (isDeletionConfirmed("release", to_release.getName())) {
			TreeObjectProject to_parentProject = getProjectOfItem(to_release);
			to_parentProject.getProject().removeRelease(to_release.getRelease().getId());
			treemodel.removeTreeObject(to_release);
			refreshViewer();
			enableSaveButton();
			//recalculate the dates
			appController.calculateDates();
			//refresh all possible open views
			appController.updateViews();
		}
	}
	
	public void addRelease() {
		IDFactory idFactory = new IDFactory();
		TreeObjectProject to_project = getProjectOfItem(getSelectedTreeObject());
		TreeParent to_releases = (TreeParent) to_project.getChildren()[0];
		
		Project project = to_project.getProject();
		
		Release lastRelease = project.getReleases().get(project.getReleases().size() - 1);
		//has to be done in this way because of the possibility to delete a release in the middle
		int releaseNumber = Integer.parseInt(lastRelease.getName().split(" ")[1]) + 1;
		int numberOfIterations = lastRelease.getIterations().size();
		
		ArrayList<Iteration> iterations = new ArrayList<Iteration>();
		ArrayList<TreeObjectIteration> to_iterations = new ArrayList<TreeObjectIteration>();
		for(int i = 1; i <= numberOfIterations; i++) {
			Iteration iteration = new Iteration(idFactory.getUniqueId(), "Iteration " + i, null, "");
			iterations.add(iteration);
			to_iterations.add(new TreeObjectIteration(iteration.getName(), iteration));
		}
		
		Release newRelease = new Release(idFactory.getUniqueId(), "Release " + releaseNumber, iterations, new Unscheduled(idFactory.getUniqueId(), null), "");
		project.addRelease(newRelease);
		
		TreeObjectRelease to_newRelease = new TreeObjectRelease(newRelease.getName(), newRelease);
		to_releases.addChild(to_newRelease);
		for (TreeObjectIteration to_iteration : to_iterations) {
			to_newRelease.addChild(to_iteration);
		}
		
		to_newRelease.addChild(new TreeObjectUnscheduled("Unscheduled", newRelease.getUnscheduled()));
		refreshViewer();
		appController.calculateDates();
		//update all possible open views
		appController.updateViews();
		appController.changesOccured();
	}

	private void removeIteration() {
		TreeObjectIteration to_iteration = (TreeObjectIteration) getSelectedTreeObject();
		if (isDeletionConfirmed("iteration", to_iteration.getName())) {
			TreeObjectRelease to_parentRelease = (TreeObjectRelease) to_iteration.getParent();
			to_parentRelease.getRelease().removeIteration(to_iteration.getIteration().getId());
			treemodel.removeTreeObject(to_iteration);
			refreshViewer();
			enableSaveButton();
			//recalculate the dates
			appController.calculateDates();
			//refresh all possible open views
			appController.updateViews();
		}
	}
	
	private void removeUserStory() {
		TreeObjectUserStory to_userStory = (TreeObjectUserStory) getSelectedTreeObject();
		if (isDeletionConfirmed("user story", to_userStory.getName())) {
			TreeObject to_parent = to_userStory.getParent();
			UserStory us = to_userStory.getUserStory();
			if (to_parent instanceof TreeObjectIteration) {
				TreeObjectIteration to_iteration = (TreeObjectIteration) to_parent;
				to_iteration.getIteration().removeUserStory(us.getId());
			} else if (to_parent instanceof TreeObjectUnscheduled) {
				TreeObjectUnscheduled to_unscheduled = (TreeObjectUnscheduled) to_parent;
				to_unscheduled.getUnscheduled().removeUserStory(us.getId());
			}
			treemodel.removeTreeObject(to_userStory);
			refreshViewer();
			appController.changesOccured();
		}		
	}

	public void changeHappened() {
		this.enableSaveButton();
	}

	public TreeViewer getViewer() {
		return viewer;
	}
	
	
}
